package jxau.jwgl.task;

import jxau.jwgl.utils.NamedThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;

/**
 * TaskExecutor
 *
 * @author LongShu 2017/06/10
 */
public class TaskExecutorImpl implements TaskExecutor {
    private static Logger logger = LoggerFactory.getLogger(TaskExecutorImpl.class);
    private long delay = 64;// 默认延时 ms

    private int poolSize = 0;
    private ScheduledExecutorService executorService;
    private ConcurrentMap<String, Future<?>> tasks = new ConcurrentHashMap<>(32);

    public int getPoolSize() {
        return poolSize;
    }

    public void setPoolSize(int poolSize) {
        this.poolSize = poolSize;
    }

    public long getDelay() {
        return delay;
    }

    public void setDelay(long delay) {
        this.delay = delay;
    }

    public <T> void addTask(RunnableCallableTask<T> task) {
        addTask(task, delay);
    }

    public <T> void addTask(RunnableCallableTask<T> task, long delay) {
        String id = task.getId();
        logger.debug("addTask:{}", id);
        ScheduledFuture<T> scheduledFuture = executorService.schedule((Callable<T>) task, delay, TimeUnit.MILLISECONDS);
        tasks.put(id, scheduledFuture);
    }

    public boolean isFinish(String id) {
        Future<?> future = tasks.get(id);
        if (future == null) {
            return true;
        }

        boolean done = future.isDone();
        logger.debug("isDone:{} {}", id, done);
        if (done) {
            tasks.remove(id);// 将完成的任务移除
        }
        return done;
    }

    @Override
    @PostConstruct
    public boolean start() {
        int processors = Runtime.getRuntime().availableProcessors();
        if (processors > poolSize) {
            poolSize = processors;
        }
        logger.info("start... poolSize:{}", poolSize);
        executorService = Executors.newScheduledThreadPool(poolSize, new NamedThreadFactory("TaskExecutor"));

        // 清理已完成的任务的任务
        TaskCleanup taskCleanup = new TaskCleanup();
        ScheduledFuture<?> cleanupFuture = executorService.scheduleWithFixedDelay(taskCleanup, delay << 2, 120, TimeUnit.SECONDS);
        tasks.put(taskCleanup.getId(), cleanupFuture);

        return true;
    }

    @Override
    @PreDestroy
    public boolean stop() {
        if (logger.isInfoEnabled()) {
            logger.info("stop... {} tasks", tasks.size());
        }
        try {
            if (!executorService.isShutdown()) {
                executorService.shutdown();
            }
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
            return false;
        } finally {
            tasks.clear();
        }
        return true;
    }

    /**
     * 清理已经完成的任务
     */
    class TaskCleanup implements RunnableCallableTask<Object> {
        private Logger logger = LoggerFactory.getLogger(this.getClass());
        public final String ID = TaskCleanup.class.getName();

        @Override
        public String getId() {
            return ID;
        }

        /**
         * clean Future help gc
         */
        void cleanup() {
            if (logger.isDebugEnabled()) {
                logger.debug("cleanup... tasks:{}", tasks.size());
            }
            try {
                for (Map.Entry<String, Future<?>> entry : tasks.entrySet()) {
                    String key = entry.getKey();
                    if (!key.equals(ID) && entry.getValue().isDone()) {
                        tasks.remove(key);
                    }
                }
            } catch (Exception e) {
                logger.warn(e.getMessage(), e);
            }
        }

        @Override
        public void run() {
            cleanup();
        }

        @Override
        public Object call() {
            cleanup();
            return null;
        }
    }

}
